/* 
 * File:   main.c
 * Author: elad
 *
 * Created on March 15, 2013, 9:49 AM
 */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <map>
#include "ftp_functions.h"
#include "ftp_codes.h"
#include "globals.h"

#define PORT "8080";
#define WORKERS 10;
#define BACKLOG 10;

char * chroot_dir = NULL;

connectionInfo createConnectionInfo() {
	connectionInfo conn;
	conn.pwd = (char *)malloc(sizeof(char) * (strlen(chroot_dir) + 1));
	strcpy(conn.pwd, chroot_dir);
	conn.ofile = NULL;
	conn.odir = NULL;
	conn.ent = NULL;
	conn.currentStatus = FTP_STATUS_WAITING_FOR_COMMEND;
	return conn;
}

int deleteConnectionInfo(connectionInfo *conn) {
	free(conn->pwd);
	free(conn);
	return 0;
}

int main(int argc, char** argv) {
  int c;
  int udp = 0;
  int select = 0;
  opterr = 0;
  
  while ((c = getopt(argc, argv, "usd:")) != -1) {
    switch(c) {
      case 'u':
        udp = 1;
        break;
      case 's':
        select = 1;
        break;
      case 'd':
    	  chroot_dir = optarg;
        break;
      case '?':
        if (optopt == 'd')
          fprintf (stderr, "Option -%c requires an argument.\n", optopt);
        else if (isprint (optopt))
          fprintf (stderr, "Unknown option `-%c'.\n", optopt);
        else
          fprintf (stderr, "Unknown option character `\\x%x'.\n",optopt);
             return 1;
      default:
        abort ();
    }
  }
  
  if (!chroot_dir) {
	  chroot_dir = (char *)malloc(sizeof(void*) * 2);
	  strcpy(chroot_dir, "/");
  }

  fprintf(stdout, "udp is: %d, select is: %d, chroot is: %s", udp, select, chroot_dir);
  return (EXIT_SUCCESS);
}

void sigchld_handler(int s)
{
  while(waitpid(-1, NULL, WNOHANG) > 0);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
  if (sa->sa_family == AF_INET) {
    return &(((struct sockaddr_in*)sa)->sin_addr);
  }
  return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int startServer(int udp, int select, char *chroot_dir) {
  int sockfd, new_fd;  // listen on sock_fd, new connection on new_fd
  struct addrinfo hints, *servinfo, *p;
  struct sockaddr_storage their_addr; // connector's address information
  socklen_t sin_size;
  struct sigaction sa;
  int yes=1;
  char s[INET6_ADDRSTRLEN];
  connectionInfo conn;
  std::map<struct sockaddr, connectionInfo> udpConnectionsMap;
  std::map<int, connectionInfo> tcpConnectionsMap;

  int rv;

  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE; // use my IP
  
  rv = getaddrinfo(NULL, PORT, &hints, &servinfo);
  if (rv != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
    return 1;
  }
  // loop through all the results and bind to the first we can

  for(p = servinfo; p != NULL; p = p->ai_next) {

    if ((sockfd = socket(p->ai_family, p->ai_socktype,
            p->ai_protocol)) == -1) {
      perror("server: socket");
      continue;
    }
    
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
            sizeof(int)) == -1) {
      perror("setsockopt");
      exit(1);
    }
    
    if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
      close(sockfd);
      perror("server: bind");
      continue;
    }
    break;
  }
  
  if (p == NULL)  {
    fprintf(stderr, "server: failed to bind\n");
    return 2;
  }
  
  freeaddrinfo(servinfo); // all done with this structure
  
  if (listen(sockfd, BACKLOG) == -1) {
    perror("listen");
    exit(1);
  }
  
  sa.sa_handler = sigchld_handler; // reap all dead processes
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = SA_RESTART;
  if (sigaction(SIGCHLD, &sa, NULL) == -1) {
    perror("sigaction");
    exit(1);
  }
  
  printf("server: waiting for connections...\n");
  
  while(1) {  // main accept() loop
    sin_size = sizeof their_addr;
    if (udp == 0) {
    	std::map<int, connectionInfo>::iterator it;
    	if (select == 1) {
    	    new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
    	    if (new_fd == -1) {
    	      perror("accept");
    	      continue;
    	    }

    	    inet_ntop(their_addr.ss_family,
    	            get_in_addr((struct sockaddr *)&their_addr),
    	            s, sizeof s);
    	    printf("server: got connection from %s\n", s);
    	    it = tcpConnectionsMap.find(new_fd);
    	    if (it == tcpConnectionsMap.end()) {
    	    	it  = tcpConnectionsMap.begin();
    	    	conn = createConnectionInfo();
    	    	tcpConnectionsMap.insert(std::pair<int, connectionInfo>(new_fd, conn));
    	    	it = tcpConnectionsMap.find(new_fd);
    	    }

    	    if (!fork()) { // this is the child process
    	      close(sockfd); // child doesn't need the listener
    	      int sendNbytes, recvNbytes;
    	      char *buf;
    	      buf = (char *)malloc(sizeof(char) * MAXDATA);
    	      recvNbytes = recv(new_fd, buf, MAXDATA - 1, 0);
    	      if (parse_command(buf, recvNbytes, &sendNbytes, &buf) > 0) {
    	    	  perror("parse command");
    	      }
    	      if (send(new_fd, buf, sendNbytes, 0) == -1) {
    	    	  perror("send");
    	      }
    	      close(new_fd);
    	      free(buf);
    	      exit(0);
    	    }
    	    close(new_fd);  // parent doesn't need this
    	} else {

    	}
    } else {
    	std::map<struct sockaddr, connectionInfo>::iterator it;
    }
  }
  
  return 0;
}

